﻿using System;
using EmbeddedSharp.Core;
using EmbeddedSharp.Core.Function.Flow;

namespace EmbeddedSharp.Scripting.Function {
    public class UserDefinedFunctionNode :
        ContextFuncNode, IUserDefinedType, ICloneable {

        public string Name {
            get {
                return this.Value;
            }
            set {
                this.Value = value;
            }
        }

        public string FuncBody {
            get;
            set;
        }

        public string ClassName {
            get;
            set;
        }

        public string[] Arguments {
            get;
            set;
        }

        public Node Body {
            get;
            set;
        }

        public string Comment {
            get;
            set;
        }

        public int LineNumber {
            get;
            set;
        }

        public IFunctionManager FunctionManager {
            get;
            set;
        }

        public IExpressionParserFactory ParserFactory {
            get;
            set;
        }

        /// <summary>
        /// Get the internal name of the function
        /// </summary>
        /// <param name="index"></param>
        /// <returns></returns>
        public string GetInternalName(int index) {
            //if (string.IsNullOrEmpty(this.ClassName)) {
            if (index == 0) {
                return this.Name;
            }

            return string.Format(
                "{0}_{1}", this.Name, index);
            //}

            //return string.Format(
            //    "{0}.{1}_{2}", this.ClassName, this.Name, index);
        }

        /// <summary>
        /// Get the reversed function
        /// </summary>
        /// <param name="index"></param>
        /// <returns></returns>
        public override FuncNode GetReversedFunction(int index) {
            string internalName = GetInternalName(index);
            return FunctionManager.GetFunction(internalName);
        }

        /// <summary>
        /// Evaluate the parameters
        /// </summary>
        public void UpdateParameters() {
            this.Arguments = new string[Parameters.Count];
            for (int index = 0; index < Parameters.Count; index++) {
                this.Arguments[index]
                    = (string)Parameters[index].Evaluate();
            }
        }

        /// <summary>
        /// Evaluate the user defined function
        /// </summary>
        /// <returns>the result</returns>
        public override object Evaluate() {
            if (Arguments.Length > Parameters.Count) {
                throw new ArgumentException(
                    "The number of defined and actual parameters are not identical.");
            }

            string expr = this.FuncBody;

            StackFrame frame = new StackFrame(this);
            for (
                int index = 0;
                index < this.Arguments.Length;
                index++) {
                string param = this.Arguments[index];
                object value = Parameters[index].Evaluate();
                if (value == null) {
                    throw new ArgumentNullException(
                        "The argument of parameter " + param + " is null");
                }

                frame.Add(param, value);
            }
            base.Context.Push(frame);

            ExpressionParser parser = this.ParserFactory.CreateParser(expr);
            Node root = parser.Parse();

            object result = null;

            try {
                result = root.Evaluate();
            } catch (ReturnException ex) {
                result = ex.Result;
            } catch (BreakException bex) {
            } catch (ContinueException cex) {
            } catch (FunctionalCLRException fex) {
            }
            base.Context.Pop();

            return result;
        }

        public bool CheckParameter(string name) {
            foreach (string p in Arguments) {
                if (name == p) {
                    return true;
                }
            }

            return false;
        }

        public bool CheckFunction() {
            if (Body == null) {
                return false;
            }

            return CheckFunction(Body);
        }

        public bool CheckFunction(Node Body) {
            if (Body is IdentifierNode) {
                if (!CheckParameter(Body.Value)) {
                    return false;
                }

                return true;
            }

            foreach (Node child in ((FuncNode)Body).Parameters) {
                if (!CheckFunction(child)) {
                    return false;
                }
            }

            return true;
        }

        #region ICloneable Members

        public object Clone() {
            UserDefinedFunctionNode shadow = base.MemberwiseClone() as UserDefinedFunctionNode;
            shadow.Parameters.Clear();

            return shadow;
        }

        #endregion
    }
}
